package com.shoulder.authorizationserver.autoconfigure;

import com.shoulder.authorizationserver.authentication.PasswordCredentialsAuthenticationConverter;
import com.shoulder.authorizationserver.authentication.PasswordCredentialsAuthenticationProvider;
import com.shoulder.authorizationserver.utils.OAuth2ConfigurerUtils;
import com.shoulder.imports.Imports;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

@Imports(value = AutoConfiguration.class)
@AutoConfiguration
@EnableWebSecurity
@EnableMethodSecurity
public class AuthorizationServerAutoConfiguration {
    /**
     * 配置认证断点的过滤器
     *
     * @param http 核心配置类
     * @return 认证过滤器链
     * @throws Exception 异常信息
     */
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http
            , UserDetailsService userDetailsService
            , PasswordEncoder passwordEncoder
            , AuthenticationEntryPoint authenticationEntryPoint)
            throws Exception {
        //默认的设置，点击源码其实就是配置了认证端点的csrf校验
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        // OAuth2AuthorizationEndpointConfigurer 提供了定制 OAuth2 授权端点 的能力。它定义了一些扩展点，让你自定义 OAuth2授权请求的预处理、主要处理和后处理逻辑
        // https://docs.spring.io/spring-authorization-server/reference/protocol-endpoints.html
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
//                .authorizationEndpoint(authorizationEndpoint -> {
                //添加一个 AuthenticationConverter（预处理器），当试图从 HttpServletRequest 中提取 OAuth2授权请求（或consent）到 OAuth2AuthorizationCodeRequestAuthenticationToken
                //或 OAuth2AuthorizationConsentAuthenticationToken 的实例时使用
//                    authorizationEndpoint.authorizationRequestConverter(authorizationRequestConverter)

                //提供对默认和（可选）添加的 AuthenticationConverter List 的访问，允许添加、删除或定制特定的 AuthenticationConverter 的能力
//                            .authorizationRequestConverters(authorizationRequestConvertersConsumer)
                //添加一个 AuthenticationProvider（主处理器），用于验证 OAuth2AuthorizationCodeRequestAuthenticationToken 或 OAuth2AuthorizationConsentAuthenticationToken
//                            .authenticationProvider(authenticationProvider)
                //提供对默认和（可选）添加的 AuthenticationProvider List 的访问，允许添加、删除或定制特定的 AuthenticationProvider
//                            .authenticationProviders(authenticationProvidersConsumer)
                // AuthenticationSuccessHandler（后处理器），用于处理 "已认证" 的 OAuth2AuthorizationCodeRequestAuthenticationToken 并返回 OAuth2AuthorizationResponse
//                            .authorizationResponseHandler(authorizationResponseHandler)
                // AuthenticationFailureHandler（后处理器），用于处理 OAuth2AuthorizationCodeRequestAuthenticationException，并返回 OAuth2Error 响应
//                            .errorResponseHandler(errorResponseHandler)
                //自定义同意（consent）页面的 URI，如果在授权请求流程中需要同意，则将资源所有者重定向至此
//                            .consentPage("/oauth2/v1/authorize");
//                })
                // OAuth2TokenEndpointConfigurer 提供了定制 OAuth2 Token 端点 的能力。它定义了扩展点，让你自定义 OAuth2 access token 请求 的预处理、主处理和后处理逻辑
                // https://docs.spring.io/spring-authorization-server/reference/protocol-endpoints.html#oauth2-device-authorization-endpoint
                .tokenEndpoint(endpointConfigurer -> endpointConfigurer.authenticationProviders(authenticationProviders
                                -> authenticationProviders.add(new PasswordCredentialsAuthenticationProvider(
                                OAuth2ConfigurerUtils.getAuthorizationService(http)
                                , OAuth2ConfigurerUtils.getTokenGenerator(http)
                                , userDetailsService, passwordEncoder
                        )))
                        // AuthenticationFailureHandler（后处理程序），用于处理 OAuth2AuthenticationException 并返回 OAuth2Error 响应
                        .errorResponseHandler(new AuthenticationEntryPointFailureHandler(authenticationEntryPoint))
                        // 提供对默认和（可选）添加的 AuthenticationConverter List 的访问，允许添加、删除或定制特定的 AuthenticationConverter 的能力
                        .accessTokenRequestConverters(converters -> converters.add(new PasswordCredentialsAuthenticationConverter())))
                //授权端点
                // 开启 OpenID Connect 1.0
                .oidc(Customizer.withDefaults());    // Enable OpenID Connect 1.0
        http
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                // 未登录时访问认证端点将被重定向至login页面
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                )
                // Accept access tokens for User Info and/or Client Registration
                // 处理使用access token访问用户信息端点和客户端注册端点
                .oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));

        return http.build();
    }

    /**
     * 配置一个基于jdbc存储的客户端信息
     * <p>
     * <a href="https://docs.spring.io/spring-authorization-server/reference/core-model-components.html#registered-client-repository">...</a>
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {

        return new JdbcRegisteredClientRepository(jdbcTemplate);
    }

    /**
     * 配置授权管理服务
     * OAuth2AuthorizationService 是一个中心组件，新的授权被存储，现有的授权被查询。
     * 当遵循特定的协议流程时,它被其他组件使用，例如：客户端认证、授权许可处理、令牌内省、令牌撤销、动态客户端注册等。
     * OAuth2AuthorizationService 的实现是 InMemoryOAuth2AuthorizationService 和 JdbcOAuth2AuthorizationService。
     * InMemoryOAuth2AuthorizationService 实现在内存中存储 OAuth2Authorization 实例
     * JdbcOAuth2AuthorizationService 是一个JDBC实现，通过使用 JdbcOperations 持久化 OAuth2Authorization 实例。
     * <p>
     * <a href="https://docs.spring.io/spring-authorization-server/reference/core-model-components.html#oauth2-authorization-service">...</a>
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 配置授权管理确认服务
     * OAuth2AuthorizationConsentService 是一个中心组件，新的授权同意被存储，现有的授权同意被查询。它主要被实现OAuth2授权请求流程的组件使用
     * OAuth2AuthorizationConsentService 的实现是 InMemoryOAuth2AuthorizationConsentService 和 JdbcOAuth2AuthorizationConsentService。
     * InMemoryOAuth2AuthorizationConsentService 实现在内存中存储 OAuth2AuthorizationConsent 实例。
     * JdbcOAuth2AuthorizationConsentService 是一个JDBC实现，通过使用 JdbcOperations 持久化 OAuth2AuthorizationConsent 实例
     * <p>
     * <a href="https://docs.spring.io/spring-authorization-server/reference/core-model-components.html#oauth2-authorization-consent-service">...</a>
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 添加认证服务器配置
     * <a href="https://docs.spring.io/spring-authorization-server/reference/configuration-model.html#configuring-authorization-server-settings">...</a>
     *
     * @return AuthorizationServerSettings
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        AuthorizationServerSettings.Builder builder = AuthorizationServerSettings.builder();
        return builder.build();

    }
}
